package com.jinyang.processor;

import com.google.auto.service.AutoService;
import com.jinyang.annotation.LjyBindView;
import com.jinyang.annotation.ClickGap;
import com.jinyang.annotation.GetTime;
import com.jinyang.annotation.NeedLogin;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

/**
 * @Author: LiuJinYang
 * @CreateDate: 2021/11/9
 * AutoService注解作用是用来生成META-INF/services/javax.annotation.processing.Processor文件的，
 */
@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    private Types mTypeUtils;
    private Messager mMessagerUtils;
    private Filer mFilerUtils;
    private Elements mElementUtils;
    /**
     * 节点信息缓存
     */
    private Map<String, List<ElementInfo>> mCache = new HashMap<>();

    /**
     * 用于初始化处理器，可以通过ProcessingEnvironment来获取一些帮助我们来处理注解的工具类
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //类信息工具类
        mTypeUtils = processingEnv.getTypeUtils();
        // Messager可以用来打印错误信息；
        mMessagerUtils = processingEnv.getMessager();
        // Filer可以用来编写新文件；
        mFilerUtils = processingEnv.getFiler();
        // Elements是一个可以处理Element的工具类。
        mElementUtils = processingEnv.getElementUtils();
    }

    /**
     * 只有一个返回值，用来指定当前正在使用的Java版本，
     * 通常return SourceVersion.latestSupported()即可
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
//        return super.getSupportedSourceVersion();
    }

    /**
     * 指明有哪些注解需要被扫描到，返回注解的全路径（包名+类名）
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
            types.add(annotation.getCanonicalName());
        }
        return types;
//        return super.getSupportedAnnotationTypes();
    }

    private Set<Class<? extends Annotation>> getSupportedAnnotations() {
        Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
        annotations.add(LjyBindView.class);
        annotations.add(ClickGap.class);
        annotations.add(GetTime.class);
        annotations.add(NeedLogin.class);
        return annotations;
    }


    /**
     * 核心方法，注解的处理和生成代码都是在这个方法中完成
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations == null || annotations.isEmpty()) {
            return false;
        }
        //扫描所有被@GetTime注解的元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(LjyBindView.class);
        if (elements == null || elements.isEmpty()) {
            return false;
        }
        //遍历被@GetTime注解的元素
        for (Element element : elements) {
            if (element.getKind() != ElementKind.FIELD) {
                throw new RuntimeException("Only classes can be annotated with " + element.getSimpleName());
            }
            // 获取节点包信息
            String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString();
            // 获取节点类信息，由于 @NeedLogin 作用方法上，所以这里使用 getEnclosingElement() 获取父节点信息
            String className = element.getEnclosingElement().getSimpleName().toString();
            // 获取节点类型
            String typeName = element.asType().toString();
            // 获取节点标记的属性名称
            String nodeName = element.getSimpleName().toString();
            // 获取注解的值
            int value = element.getAnnotation(LjyBindView.class).value();

            // 打印
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "packageName：" + packageName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "className：" + className);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "typeName：" + typeName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "nodeName：" + nodeName);
            mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "value：" + value);

            // 缓存KEY
            String key = packageName + "." + className;
            // 缓存节点信息
            List<ElementInfo> nodeInfos = mCache.get(key);
            if (nodeInfos == null) {
                nodeInfos = new ArrayList<>();
                nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value));
                // 缓存
                mCache.put(key, nodeInfos);
            } else {
                nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value));
            }

        }

        // 判断临时缓存是否不为空
        if (!mCache.isEmpty()) {
            // 遍历临时缓存文件
            for (Map.Entry<String, List<ElementInfo>> stringListEntry : mCache.entrySet()) {
                try {
                    // 创建文件
                    createFile(stringListEntry.getValue());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 创建文件，自动生成代码
     * javapoet的具体使用教程请参考javapoet官方文档
     */
    private void createFile(List<ElementInfo> infos) throws IOException {
        ElementInfo info = infos.get(0);
        // 生成的文件名(类名)
        String className = info.getClassName() + "__ViewBinding";
        // 方法参数
        ParameterSpec parameterSpec = ParameterSpec.builder(
                ClassName.get(info.getPackageName(), info.getClassName()), "target")
                .build();
        // 方法
        MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(parameterSpec)
                .returns(void.class);
        // 给方法添加代码块
        for (ElementInfo nodeInfo : infos) {
            // target.textView = (TextView) target.findViewByID(R.id.text_view);
            methodSpecBuilder.addStatement("target.$L = ($L)target.findViewById($L)",
                    nodeInfo.getNodeName(),
                    nodeInfo.getTypeName(),
                    nodeInfo.getValue());
        }
        // 类
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(methodSpecBuilder.build())
                .build();
        // 生成文件
        JavaFile.builder(info.getPackageName(), typeSpec)
                .build()
                .writeTo(mFilerUtils);
    }
}
